<?php
/**
 * NetBiz Framework
 * Copyright (C) 2008 - 2009 NetBiz
 * 
 * All rights reserved.
 */

class FRM_Application {
	
	const HOOK_BEFORE = 1;
	const HOOK_AFTER  = 2;

	protected $_modules;
	private $_hooks = array();

	function __construct()
	{
		// Default modules setup
		$this->_modules = new FRM_ModuleList();
		$this->_modules->addModule('Paths');
		$this->_modules->addModule('Config');
		$this->_modules->addModule('Mail');
		$this->_modules->addModule('Log');
		$this->_modules->addModule('Timezone');
		$this->_modules->addModule('Request');
		$this->_modules->addModule('TemplateEngine');
		$this->_modules->addModule('Unicode');
		$this->_modules->addModule('ORM');
		$this->_modules->addModule('Session');
		$this->_modules->addModule('I18N');
		$this->_modules->addModule('Ajax');
	}

	protected final function addHook($moduleName,$methodName,$hook_position = self::HOOK_AFTER)
	{
		if ($hook_position != self::HOOK_AFTER)
		{
			$hook_position = self::HOOK_BEFORE;
		}

		if ($this->_modules->exists($moduleName))
		{
			$this->_hooks[$moduleName][$hook_position] = $methodName;
		}
	}

	public final function run()
	{
		// :TODO: pass the behaviour of running something to the request
		$controller = FRM_String::capitalizeSymbol(FRM_Request::getController());		
		if (!$controller)
		{
			$controller = FRM_Config::get('APPLICATION','DEFAULT_CONTROLLER');
		}
		$action = FRM_Request::getAction();
		if (!$action)
		{
			$action = FRM_Config::get('APPLICATION','DEFAULT_ACTION');
		}

		$ControllerName = $controller."Controller";
		$ActionName 	= $action."Action";

		if (class_exists($ControllerName))
		{
			$objHandlerController = new $ControllerName();
			
			if(method_exists($objHandlerController,$ActionName) or method_exists($objHandlerController,'__call'))
			{
				$objHandlerController->$ActionName();
			}
			else
			{
				throw new FRM_Exception("Cannot find $action in controller $ControllerName",FRM_Exception::NO_ACTION);
			}
		}
		else
		{
			throw new FRM_Exception("Cannot find controller $ControllerName",FRM_Exception::NO_CONTROLLER);
		}
	}

	public function processException(FRM_Exception $e)
	{
		// Try first to log the exception
		try
		{
			if ($logger = FRM::load('logger'))
			{
				$logger->err($e);
			}
		}
		catch (FRM_Exception $e2)
		{
		}

		if (FRM_Config::get('REQUEST','CLI'))
		{
			echo 'ERROR : Exception ('.$e->getCode().")\n".$e->getMessage()."\nBACKTRACE :\n".
				$e->getTraceAsString()."\n";
		}
		else
		{
			echo '<hr/><h1>An exception has been raised</h1>';
			echo '<ul><li>Code : '.$e->getCode().'</li><li>Message : '.$e->getMessage().'</li>';
			echo '<li>In '.$e->getFile().' ('.$e->getLine().')</li>';
			echo '</ul><dr><strong>Backtrace :</strong><br/></dr>';
			echo '<dd>';
			echo nl2br($e->getTraceAsString());
			echo '</dd>';

			if ($extra = $e->getExtra())
			{
				echo '<br/><br/><dr><strong>Extra info :</strong><br/></dr>';
				echo '<dd>';
				echo nl2br($extra);
				echo '</dd>';
			}
		}
	}

	public final function initialize()
	{
		foreach($this->_modules as $m)
		{
			if ($method = (isset($this->_hooks[$m][self::HOOK_BEFORE]) ? $this->_hooks[$m][self::HOOK_BEFORE] : FALSE))
			{
				$this->$method();
			}

			$method = 'initialize'.$m;
			$this->$method();

			if ($method = (isset($this->_hooks[$m][self::HOOK_AFTER]) ? $this->_hooks[$m][self::HOOK_AFTER] : FALSE))
			{
				$this->$method();
			}
		}
		
		$this->cleanupInitialization();
	}

	protected final function initializePaths()
	{
		// TODO fix that for application stuff, see if it's really useful (not sure)
		set_include_path('.' . PATH_SEPARATOR . APPLICATION_DIR . '/' .
			PATH_SEPARATOR . FRAMEWORK_DIR . '/external/' .
			PATH_SEPARATOR . FRAMEWORK_DIR . '/external/PEAR/' .
			PATH_SEPARATOR . get_include_path());
		require_once(FRAMEWORK_DIR.'/class/FRM.php');
	}

	protected final function initializeConfig()
	{
		FRM_Config::loadConfig(FRAMEWORK_DIR.'/config/c.php');
	}

	protected final function initializeTimezone()
	{
		date_default_timezone_set(FRM_Config::get('I18N','TIMEZONE'));
	}

	protected final function initializeRequest()
	{
		$is_cli = ( strcmp(php_sapi_name(),'cli') == 0 );
		FRM_Config::set('REQUEST','CLI',$is_cli);
		
		if ($is_cli)
		{
			// No time limit for CLI calls
			set_time_limit(0);

			// Let's disable useless modules in case of a CLI call
			$this->_modules->disableModule('Session');
			$this->_modules->disableModule('I18N');
		}

		FRM_Request::initialize($is_cli);
	}

	protected final function initializeORM()
	{
		$options = &PEAR::getStaticProperty( 'DB_DataObject', 'options' );
		$options = array(
			'schema_location'  				 => APPLICATION_DIR.'/model',
			'require_prefix'                 => 'model',
			'class_prefix'                   => 'DataObjects_',
			'db_driver'			 => FRM_Config::get('DB','DRIVER'),
			'dont_die'			 => TRUE
		);
		$options = array_merge($options,FRM_Config::get('DB','DSN'));
		
		$mdb2_options = &PEAR::getStaticProperty( 'MDB2', 'options' );
		if (!is_array($mdb2_options)) { $mdb2_options = array(); }
		$mdb2_options['quote_identifier'] = true;
		
		FRM_Config::lock('DB');
	}

	protected final function initializeTemplateEngine()
	{
		$smarty = new FRM_Smarty();
		$smarty->left_delimiter =  '{';
		$smarty->right_delimiter =  '}';
		
		$smarty->caching      = FALSE;
		$smarty->template_dir = realpath(FRM_Config::get('FS','TEMPLATE_DIR'));
		$smarty->compile_dir  = realpath(FRM_Config::get('FS','TEMPLATE_C_DIR'));
		$smarty->config_dir  = $smarty->template_dir;
		$smarty->config_overwrite = TRUE;
		FRM::register("smarty", $smarty);
		
		FRM_Config::lock('FS','TEMPLATE_DIR');
		FRM_Config::lock('FS','TEMPLATE_C_DIR');
	}

	protected final function initializeUnicode()
	{
		if (FRM_Config::get('I18N','UTF8_SUPPORT'))
		{
			// For compatibility with IE6,7 we don't want apache to force-send a charset
			// HTTP header for AJAX query, otherwise, it's causing a JS error c00ce56e
			// So here, we enter the block only if we're NOT answering a XOAD ajax call
			if (!(isset($_GET['xoadCall']) && strcasecmp($_GET['xoadCall'], 'true') == 0))
			{
				// We set utf8 as default charset to  be returned with the content
				// this can be overriden by header("ContentType...);
				//ini_set("default_charset","utf8");
			}
		}
		
		// [FIXME] Improve this document wise when the FRM_Document class
		// will be ready
		try
		{
			$smarty = FRM::load('smarty');
			$smarty->assign("utf8_charset_meta",
				'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">');
			$smarty->assign("utf8_form_attribute",
				' accept-encoding="utf-8" ');
		}
		catch (FRM_Exception $e)
		{
		}
	}

	protected final function initializeSession()
	{
		if (FRM_Config::get('SESSION','AUTOSTART'))
		{
			FRM_Session::initialize();
		}

		FRM_Config::lock('SESSION','AUTOSTART');
	}

	protected final function initializeI18N()
	{
		FRM_MultiLanguage::initialize(FRM_Config::get('I18N','DEFAULT_LANGUAGE'),
					FRM_Config::get('I18N','AUTHORIZED_LANGUAGES'));
		
		// Should we change the current language?
		$new_lang = FRM_Request::getString(FRM_Config::get('I18N','DEFAULT_LANGUAGE_PARAMETER_NAME'));
		
		$url = $_SERVER['REQUEST_URI'];
		$temp = explode('?', $url);
		if (isset($temp[1])){
			$temp = explode('=', $temp[1]);
			$new_lang = $temp[1];
		}
		
		if($new_lang!='') {
			FRM_MultiLanguage::setLanguage($new_lang);
		}
	}

	protected final function initializeAjax()
	{
		// Just load the class once to trigger auto-response
		class_exists('FRM_XOAD');
	}

	protected final function initializeLog()
	{
		if (isset($_GET['xoadCall']) and strcasecmp($_GET['xoadCall'], 'true') == 0)
		{
			// This is an XOAD call, we have to silence the logging system to avoid
			// outputting stuff to the standard input
			FRM_Config::set('LOG','SILENCE',TRUE);
		}

		$logh = new FRM_Log();
		$logh->addLogger(FRM_Config::get('LOG','FACILITY'),FRM_Config::get('LOG','PARAM'));
		FRM::register('logger',$logh);
	}

	protected final function initializeMail()
	{
		FRM_Mail::initialize();
	}
	
	private function cleanupInitialization()
	{
		// Export constants configuration variable to proper PHP constants
		FRM_Config::export();
	}
}


class FRM_ModuleList implements Iterator
{
	private $_modules = array();
	private $_mod_activation = array();
	private $_mod_ids = array();
	private $_last_id = 0;
	private $_nb_item = 0;

	// Iterator attributes
	private $_current_id = 0;
	private $_processed = array();

	public function disableModule($moduleName)
	{
		$keys = array_keys($this->_modules,$moduleName);

		foreach($keys as $k)
		{
			$this->_mod_activation[$k] = FALSE;
		}
	}

	public function addModule($moduleName,$before = '')
	{
		$moduleName = strval($moduleName);

		if ($moduleName != '')
		{
			$this->disableModule($moduleName);
			$poss = array_keys($this->_modules,$before);
			if (empty($poss))
			{
				$this->_modules[] = $moduleName;
				$this->_mod_activation[] = TRUE;
				$this->_mod_ids[] = ++$this->_last_id;

				++$this->_nb_item;
			}
			else
			{
				$pos = $poss[count($poss)-1];
				array_splice($this->_modules, $pos, $this->_nb_item, array_merge(array($moduleName), array_slice($this->_modules, $pos)));
				$true_array = array();
				$true_array[] = TRUE;
				array_splice($this->_mod_activation, $pos, $this->_nb_item, array_merge($true_array, array_slice($this->_mod_activation, $pos)));
				$id_array = array();
				$id_array[] = ++$this->_last_id;
				array_splice($this->_mod_ids, $pos, $this->_nb_item, array_merge($id_array, array_slice($this->_mod_ids, $pos)));

				++$this->_nb_item;
			}
	
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}

	public function exists($moduleName)
	{
		return in_array($moduleName,$this->_modules);
	}

	// Iterator methods
	public function rewind()
	{
		$this->_current_id = ($this->_nb_item > 0 ? $this->_mod_ids[0] : 0);
		$this->_processed = array();
	}

	public function current()
	{
		$pos = array_search($this->_current_id,$this->_mod_ids);

		if ($pos !== FALSE)
		{
			$res = $this->_modules[$pos];
		}
		else
		{
			$res = FALSE;
		}

		return $res;
	}

	public function key()
	{
		return $this->_current_id;
	}

	public function next()
	{
		// Next item based on current_id index, if active
		$pos = array_search($this->_current_id,$this->_mod_ids);

		$found = FALSE;
		if ($pos !== FALSE)
		{
			while(!$found and ++$pos < $this->_nb_item)
			{
				$found = ($this->_mod_activation[$pos] and !in_array($this->_modules[$pos],$this->_processed));
			}
		}

		if ($found)
		{
			$var = $this->_modules[$pos];
			$this->_current_id = $this->_mod_ids[$pos];
			$this->_processed[] = $var;
		}
		else
		{
			$var = FALSE;
			$this->_current_id = 0;
		}

		return $var;
	}

	public function valid()
	{
		return ( $this->_current_id != 0 );
	}
}